이번에는 leaflet과 crosstalk을 활용하여 지도 시각화 및 위젯 간 상호작용을 다뤄볼 것입니다. leaflet은 지도 시각화를 위한 자바스크립트의 오픈소스 라이브러리 Leaflet.js의 R 인터페이스입니다. crosstalk은 하나의 공유 데이터를 중심으로 여러 위젯들을 연결하며, linked brushing, filtering 등의 기능을 제공합니다. 이번 실습에서 사용하는 crosstalk, leaflet, d3scatter 패키지는 아래 깃허브에서 받아주시기 바랍니다.
devtools::install_github("dmurdoch/leaflet@crosstalk4")
devtools::install_github("rstudio/leaflet")
devtools::install_github("jcheng5/d3scatter")
지도를 생성하는 코드는 매우 간단하며 직관적입니다. leaflet() %>% addTiles()로 지도를 그리고 mymap이라는 변수에 저장하였습니다. 왼쪽 지도는 이 결과를 그대로 출력한 것이고, 오른쪽 지도는 setView() 함수로 좌표와 확대를 지정하여 출력한 것입니다. crosstalk::bscols() 함수는 단순히 지도를 나란히 배열하기 위해서 사용하였으며, 지도에는 아무런 영향을 미치지 않습니다.
library(tidyverse)
library(leaflet)
mymap = leaflet() %>% addTiles()
crosstalk::bscols(
mymap,
mymap %>% setView(126.982, 37.5502, zoom=10)
)
leaflet을 통해서 손쉽게 지도를 그릴 수 있을 뿐 아니라, 지도 위에 데이터를 표현하는 것도 가능합니다. 다음은 지도 위에 점을 표현하는 예제들입니다. 위경도로 표현된 좌표가 있다면 손쉽게 데이터를 지도 위에 표현할 수 있습니다.
points1 = mymap %>%
addMarkers(lng=c(126.882, 126.982, 127.082),
lat=c(37.4502, 37.5502, 37.6502),
label=c('예시1','예시2','예시3'))
points2 = mymap %>%
addMarkers(data = quakes[1:20,], lng=~long, lat=~lat, label=~as.character(mag))
crosstalk::bscols(points1,points2)
지리 데이터는 .shp, .json, .csv 등 다양한 형태로 존재합니다. 이번에는 .json 형식을 파일을 가지고 실습을 진행해보겠습니다. 실습에 사용할 데이터는 2016년 국회의원 총선의 서울 지역 개표결과 데이터입니다. 전처리 geojsonio패키지를 통해서 SpatialPolygonsDataFrame 클래스로 파일을 읽어옵니다. SpatialPolygonsDataFrame은 좌표계, 폴리곤 좌표 등의 지리정보와 일반적인 데이터프레임을 결합해놓은 클래스입니다. election@polygons를 통해서 폴리곤 리스트에 접근할 수 있고, election@data를 통해서 데이터프레임에 접근할 수 있습니다.
election = geojsonio::geojson_read("data/election.json", what = "sp")
cat("class(election) : ", class(election))
## class(election) : SpatialPolygonsDataFrame
cat("class(election@polygons) : ", class(election@polygons))
## class(election@polygons) : list
cat("class(election@data) : ", class(election@data))
## class(election@data) : data.frame
# 데이터프레임에 간단한 전처리를 해준 예시
colnames(election@data) = colnames(election@data) %>% str_replace(" ","")
지도에 폴리곤을 표현하는 것은 점을 찍는 일만큼이나 간단합니다. 왼쪽 예제에서 볼 수 있듯이 addPolygons()에 데이터를 전달해주는 것만로 폴리곤을 추가할 수 있습니다. 원하는 변수를 전달하면 간단한 라벨을 표현하는 것도 가능합니다. 오른쪽 예제에서는 경계선과 폴리곤 내부 색, 하이라이트를 추가하고, 라벨을 수정하고, 선거구별 폴리라인과 범례를 추가적으로 겹쳐주었습니다. add~ 함수들이 하나의 레이어를 공유한다면, leaflet()안에 먼저 데이터를 전달하는 것도 좋습니다.
poly1 = mymap %>% addPolygons(data=election,label=~당선)
# 지도에 색을 입히기 위해서 파레트 생성
pal = colorFactor(c("#0D7440", "#2A88C5", "#C10D0D"), c("국민의당", "더불어민주당","새누리당"))
# 툴팁에 담을 정보 생성
labs = lapply(seq(nrow(election@data)), function(i) {
paste0( '<p><b>', election@data[i, "선거구"]," : ",election@data[i, "읍면동명"], '</b></p><p>',
"<b>당선 : ", election@data[i, "당선"], '</b><p></p>',
"국민의당 : ", election@data[i, "국민의당"],'</p><p>',
"더불어민주당 : ", election@data[i, "더불어민주당"], '</p><p>',
"새누리당 : ", election@data[i, "새누리당"], '</p>')
})
# 선거구 경계 생성
election_gu = maptools::unionSpatialPolygons(election, election$선거구)
poly2 = leaflet(election) %>%
addTiles %>%
addPolygons(
# 폴리곤 내부
fillColor = ~pal(당선), fillOpacity = 0.5,
# 폴리곤 경계
weight = 1, opacity = 1, color = "white", dashArray=3,
# 라벨
label=lapply(labs, htmltools::HTML),
# 하이라이트
highlightOptions = highlightOptions(
color = "#00ff00", opacity = 1, weight = 2, fillOpacity = 1,
bringToFront = T, sendToBack = T))%>%
addLegend(pal = pal, values = ~당선, opacity = 0.7, title = NULL, position = "bottomright") %>%
addPolylines(
data=election_gu,
weight = 1.5,
opacity = 1,
color = "black")
crosstalk::bscols(poly1, poly2)
Linked brushing은 하나의 플롯에서 발생한 브러싱을 연결된 다른 플롯들에도 적용하는 기능입니다. 먼저 crosstalk::SharedData로 데이터프레임을 감싸 여러 위젯들을 연결할 공유 데이터를 생성해줍니다. 이 객체를 이용해서 두 개의 그래프를 그렸습니다. 이후 subplot 함수로 두 개의 그래프를 묶어주고, highlight에 "plotly_selected"를 전달해주면 됩니다. 한 쪽 플롯에서 점들을 선택하면 다른 쪽 플롯에서도 하이라이트되는 것을 확인할 수 있습니다. crosstalk의 SharedData가 아닌 일반 데이터프레임으로 플롯을 그린다면 당연히 이 기능은 작동하지 않습니다.
library(crosstalk)
library(plotly)
# election@data를 SharedData로 감싸 공유 데이터를 생성
shared_elec = SharedData$new(election@data, ~id)
p1 = plot_ly(shared_elec, x = ~소득, y = ~노령화지수, legendgroup=~당선) %>%
add_markers(color=~당선, colors=c("#0D7440", "#2A88C5", "#C10D0D"), opacity=0.6)
p2 = plot_ly(shared_elec, x = ~장애등급별.장애인현황, y = ~보육시설, legendgroup=~당선) %>%
add_markers(color=~당선, colors=c("#0D7440", "#2A88C5", "#C10D0D"), opacity=0.6)
subplot(p1,p2) %>%
layout(xaxis = list(title = "소득"),
yaxis = list(title = "노령화지수"),
xaxis2 = list(title = "장애인현황"),
yaxis2 = list(title = "보육시설"),
height=400, width=850) %>%
highlight("plotly_selected")
crosstalk은 서로 다른 종류의 위젯들 간에도 잘 작동합니다. 다만, 여러 위젯들이 요구하는 입력 형식이 다르기 때문에 약간의 조작이 더 필요합니다. 다음은 지도와 산점도, 테이블을 연결한 예제입니다. election 객체의 클래스는 SpatialPolygonsDataFame입니다. plot_ly 함수는 SpatialPolygonsDataFrame 클래스에 대해서 작동하지 않으므로, election@data를 별도로 공유 데이터로 만들어주면서 group=shared_elec$groupName() 인자를 전달했습니다. 즉 동일한 키에 의해 shared_elec과 shared_elec_df가 한 그룹으로 연결된 상태이고, 연동이 잘 이루어지는 것을 볼 수 있습니다. shared_elec_dt 역시 그룹을 지정하여 연동시켰습니다.
library(d3scatter)
shared_elec = SharedData$new(election)
shared_elec_df = SharedData$new(election@data, group=shared_elec$groupName())
shared_elec_dt = crosstalk::SharedData$new(election@data %>%
select(선거구,당선,읍면동명,소득,노령화지수), group=shared_elec$groupName())
DT::datatable(shared_elec_dt, width="100%")
bscols(
d3scatter(data=shared_elec_df, x=~소득,y=~노령화지수,color=~당선, height=300, width="100%"),
d3scatter(data=shared_elec_df, x=~장애등급별.장애인현황,y=~사업체수,color=~당선, height=300, width="100%")
)
leaflet(shared_elec) %>%
addProviderTiles("CartoDB.Positron") %>%
setView(126.982, 37.5502, zoom=10) %>%
addPolygons(
fillColor = ~pal(당선),
weight = 1,
opacity = 1,
color = "white",
dashArray=3,
fillOpacity = 0.5,
label = lapply(labs, htmltools::HTML)) %>%
addLegend(pal = pal, values = ~당선, opacity = 0.7, title = NULL, position = "bottomright") %>%
addPolylines(
data=election_gu,
weight = 1.5,
opacity = 1,
color = "black")
필터링은 말 그대로 특정 데이터 포인트를 필터링하는 기능이며, 크게 설명할 것이 없습니다. 예제와 함께 보겠습니다. filter_ 함수에 SharedData 넣어서 데이터에 맞는 형태의 필터 인풋을 만들어주면, 데이터를 공유하는 모든 위젯에 필터링이 적용됩니다.
bscols(
list(filter_checkbox("당선", "당선", shared_elec_df, ~당선, inline=T),
filter_slider("소득", "소득", shared_elec_df, ~소득, width="100%"),
filter_select("동", "동", shared_elec_df, ~읍면동명)),
d3scatter(shared_elec_df, ~소득, ~기초수급자인원수, ~당선, width="100%", height=250),
d3scatter(shared_elec_df, ~소득, ~기초수급자인원수, ~당선, width="100%", height=250)
)
DT::datatable(shared_elec_dt, width="100%")
flexdashboard마지막으로, 이번 장에서 다룬 내용들과 flexdashboard를 활용하여 대시보드를 구현하였습니다. 결과물과 코드는 여기에서 확인하실 수 있습니다.